#include <stdio.h>
#include <stdlib.h>
#include <yaml.h>
#include <limits.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <bsd/string.h>

#include "conf-yaml.h"
#include "conf-yaml-loader.h"

// 支持的版本号
#define YAML_VERSION_MAJOR 1
#define YAML_VERSION_MINOR 1

/* The maximum level of recursion allowed while parsing the YAML
 * file. */
#define RECURSION_LIMIT 128
/* Sometimes we'll have to create a node name on the fly (integer
 * conversion, etc), so this is a default length to allocate that will
 * work most of the time. */
#define DEFAULT_NAME_LEN 16

#define MANGLE_ERRORS_MAX 10
static int mangle_errors = 0;

// yaml 配置文件路径
static char *conf_dirname = NULL;

static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state);

/* Configuration processing states. */
enum conf_state {
    CONF_KEY = 0,
    CONF_VAL,
    CONF_INCLUDE,
};

/**
 * 转换不支持的字符 _ 转换成 -
 */
static void
Mangle(char *string)
{
    char *c;

    while ((c = strchr(string, '_')))
        *c = '-';

    return;
}

/**
 * 设置配置文件的目录，include引入的时候，如果是相对路径，则根据设置的路径拼接
 *      全局变量 conf_dirname 赋值
 * 参数   配置文件路径
 */
static void
ConfYamlSetConfDirname(const char *filename)
{
    char *ep;

    ep = strrchr(filename, '\\');
    if (ep == NULL)
        ep = strrchr(filename, '/');

    if (ep == NULL) { // 没找到，用 . 作为当前目录
        conf_dirname = strdup(".");
        if (conf_dirname == NULL) {
            fprintf(stderr, "ERROR: Failed to allocate memory while loading configuration.\n");
        }
    }
    else { // 找到，copy出目录副本，并报错
        conf_dirname = strdup(filename);
        if (conf_dirname == NULL) {
            fprintf(stderr, "ERROR: Failed to allocate memory while loading configuration.\n");
        }
        conf_dirname[ep - filename] = '\0';
    }
}

/**
 * 在配置里引入一个文件
 * 参数
 *     parent    解析的数据都放在此节点
 *     filename  引入的文件名
 * 返回值   0 - 解析成功
 *         1 - 解析失败
 */
int ConfYamlHandleInclude(ConfNode *parent, const char *filename)
{
    yaml_parser_t parser; 
    char include_filename[PATH_MAX];
    FILE *file = NULL;
    int ret = -1;

    // 重新初始化新的解析句柄
    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "Failed to initialize YAML parser\n");
        return -1;
    }

    // 判断是否为绝对路径
    if (PathIsAbsolute(filename)) {
        strlcpy(include_filename, filename, sizeof(include_filename));
    }
    else {
        // 拼写出路径
        snprintf(include_filename, sizeof(include_filename), "%s/%s",
            conf_dirname, filename);
    }

    file = fopen(include_filename, "r");
    if (file == NULL) {
        fprintf(stderr, "Failed to open configuration include file %s: %s\n", include_filename,
                strerror(errno));
        goto done;
    }

    // 设置要解析的文件句柄
    yaml_parser_set_input_file(&parser, file);

    // include引入的文件 解析
    if (ConfYamlParse(&parser, parent, 0, 0, 0) != 0) {
        fprintf(stderr, "Failed to include configuration file %s\n", filename);
        goto done;
    }

    ret = 0;

done:
    yaml_parser_delete(&parser);
    if (file != NULL) {
        fclose(file);
    }

    return ret;
}

/**
 * 解析yaml
 *
 * 参数     parser 活动的 yaml_parser_t 指针
 *         parent 父节点 node
 *         inseq 列表的解析
 *         rlevel 
 *         state 当前 解析状态
 *
 * 返回值：0 - success, -1 - failure.
 *
 * 调用的地方： 
 *        1、 首次文档解析
 *        2、 include引入的文件 解析
 *        3、 列表的解析
 *        4、 mapping的解析
 */
static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state)
{
    ConfNode *node = parent;
    yaml_event_t event;
    memset(&event, 0, sizeof(event));
    int done = 0;
    int seq_idx = 0;   // 列表的索引, 从 0 开始
    int retval = 0;
    int was_empty = -1;
    int include_count = 0;

    // rlevel 给递归层级增加，从新调用一次，就增加一次
    if (rlevel++ > RECURSION_LIMIT) {
        fprintf(stderr, "Recursion limit reached while parsing "
                   "configuration file, aborting.\n");
        return -1;
    }

    while (!done) {
        if (!yaml_parser_parse(parser, &event)) {  // 解析下一个事件
            fprintf(stderr, "Failed to parse configuration file at line %" PRIuMAX ": %s\n",
                    (uintmax_t)parser->problem_mark.line, parser->problem);
            retval = -1;
            break;
        }

        if (event.type == YAML_DOCUMENT_START_EVENT) {   // 文档事件
            // fprintf(stdout, "event.type=YAML_DOCUMENT_START_EVENT; state=%d\n", state);
            /* 验证 YAML 版本号 */
            yaml_version_directive_t *ver =
                event.data.document_start.version_directive;
            if (ver == NULL) {
                fprintf(stderr, "ERROR: Invalid configuration file.\n");
                fprintf(stderr, "The configuration file must begin with the following two lines: %%YAML "
                           "1.1 and ---\n");
                goto fail;
            }
            int major = ver->major;
            int minor = ver->minor;
            if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) {
                fprintf(stderr, "ERROR: Invalid YAML version.  Must be 1.1\n");
                goto fail;
            }
        }
        else if (event.type == YAML_SCALAR_EVENT) { // 标量
            char *value = (char *)event.data.scalar.value;   // 值
            char *tag = (char *)event.data.scalar.tag;       // 键
            // fprintf(stdout, "event.type=YAML_SCALAR_EVENT; state=%d; value=%s; "
            //    "tag=%s; inseq=%d\n", state, value, tag, inseq);

            // 跳过没有值的空标量
            if (state == CONF_KEY && strlen(value) == 0) {
                goto next;
            }

            /* If the value is unquoted, certain strings in YAML represent NULL. */
            // 如果值没有引用，YAML中有些字符串表示NULL
            if ((inseq || state == CONF_VAL) &&
                    event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) { 
                // 表示NULL的一些值
                if (strlen(value) == 0 || strcmp(value, "~") == 0 || strcmp(value, "null") == 0 ||
                        strcmp(value, "Null") == 0 || strcmp(value, "NULL") == 0) {
                    value = NULL;
                }
            }
            // 列表的处理
            if (inseq) {
                // 引入文件状态的处理
                if (state == CONF_INCLUDE) {
                    if (value != NULL) {
                        // fprintf(stdout, "Including configuration file %s.\n", value);
                        if (ConfYamlHandleInclude(parent, value) != 0) {
                            goto fail;
                        }
                    }
                    goto next;
                }
                // 把 索引作为 路径节点名字， 从 0 开始
                char sequence_node_name[DEFAULT_NAME_LEN];
                snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
                ConfNode *seq_node = NULL;
                if (was_empty < 0) {
                    // initialize was_empty
                    if (TAILQ_EMPTY(&parent->head)) {
                        was_empty = 1;
                    } else {
                        was_empty = 0;
                    }
                }
                // 判断节点是否已经存在
                if (was_empty == 0) {
                    seq_node = ConfNodeLookupChild(parent, sequence_node_name);
                }
                if (seq_node != NULL) {
                    // 这里先从兄弟队列里删除，20行之后等会还会加上，这里不会清数据
                    TAILQ_REMOVE(&parent->head, seq_node, next);
                }
                else {
                    // 创建节点，并赋值
                    seq_node = ConfNodeNew();
                    if (unlikely(seq_node == NULL)) {
                        goto fail;
                    }
                    seq_node->name = strdup(sequence_node_name);
                    if (unlikely(seq_node->name == NULL)) {
                        free(seq_node);
                        goto fail;
                    }
                    if (value != NULL) { // 如果value有值，则赋值
                        seq_node->val = strdup(value);
                        if (unlikely(seq_node->val == NULL)) {
                            free(seq_node->name);
                            goto fail;
                        }
                    } else {   // 否则为 NULL
                        seq_node->val = NULL;
                    }
                }
                // 把节点插入到
                TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
            }
            else {
                if (state == CONF_INCLUDE) {
                    // fprintf(stdout, "Including configuration file %s.\n", value);
                    // 解析子文件，parent用来接收解析的数据，把值给到处理函数
                    if (ConfYamlHandleInclude(parent, value) != 0) {
                        goto fail;
                    }
                    // 解析完了，修改解析状态为：需要解析KEY
                    state = CONF_KEY;
                }
                else if (state == CONF_KEY) {

                    if (strcmp(value, "include") == 0) {
                        // 修改状态：包含子文件状态
                        state = CONF_INCLUDE;
                        if (++include_count > 1) {
                            fprintf(stderr, "Multipline \"include\" fields at the same level are "
                                         "deprecated and will not work in Suricata 8, please move "
                                         "to an array of include files: line: %zu\n",
                                    parser->mark.line);
                        }
                        goto next;
                    }

                    if (parent->is_seq) {
                        if (parent->val == NULL) {
                            parent->val = strdup(value);
                            if (parent->val && strchr(parent->val, '_'))
                                // _ 转换成 - 
                                Mangle(parent->val);
                        }
                    }

                    if (strchr(value, '.') != NULL) {
                        node = ConfNodeGetNodeOrCreate(parent, value, 0);
                        if (node == NULL) {
                            /* Error message already logged. */
                            goto fail;
                        }
                    } else {
                        ConfNode *existing = ConfNodeLookupChild(parent, value);
                        if (existing != NULL) {
                            if (!existing->final) {
                                // fprintf(stdout, "Configuration node '%s' redefined.\n", existing->name);
                                ConfNodePrune(existing);
                            }
                            node = existing;
                        } else {
                            node = ConfNodeNew();
                            node->name = strdup(value);
                            node->parent = parent;
                            if (node->name && strchr(node->name, '_')) {
                                if (!(parent->name &&
                                            ((strcmp(parent->name, "address-groups") == 0) ||
                                                    (strcmp(parent->name, "port-groups") == 0)))) {
                                    // _ 转换成 - 
                                    Mangle(node->name);
                                    if (mangle_errors < MANGLE_ERRORS_MAX) {
                                        fprintf(stderr,
                                                "%s is deprecated. Please use %s on line %" PRIuMAX 
                                                ".\n",
                                                value, node->name,
                                                (uintmax_t)parser->mark.line + 1);
                                        mangle_errors++;
                                        if (mangle_errors >= MANGLE_ERRORS_MAX)
                                            fprintf(stderr,
                                                    "not showing more "
                                                    "parameter name warnings.\n");
                                    }
                                }
                            }
                            TAILQ_INSERT_TAIL(&parent->head, node, next);
                        }
                    }
                    // 修改状态，需要获得值
                    state = CONF_VAL;
                }
                else {
                    if (value != NULL && (tag != NULL) && (strcmp(tag, "!include") == 0)) {
                        // fprintf(stdout, "Including configuration file %s at "
                        //    "parent node %s.\n", value, node->name);
                        if (ConfYamlHandleInclude(node, value) != 0)
                            goto fail;
                    } else if (!node->final && value != NULL) {
                        if (node->val != NULL)
                            free(node->val);
                        node->val = strdup(value);
                    }
                    // 修改状态：需要解析KEY
                    state = CONF_KEY;
                }
            }
        }
        else if (event.type == YAML_SEQUENCE_START_EVENT) {
            // fprintf(stdout, "event.type=YAML_SEQUENCE_START_EVENT; state=%d\n", state);
            // 如果处理列表是include列表，使用 当前节点
            // 状态如果是 CONF_INCLUDE   下一级解析传递 parent节点，否则传递 当前节点
            // inseq 标记为 1 此次处理的是被别的文件引入的
            // 列表 解析进入 递归调用
            if (ConfYamlParse(parser, state == CONF_INCLUDE ? parent : node, 1, rlevel,
                        state == CONF_INCLUDE ? CONF_INCLUDE : 0) != 0)
                goto fail;
            // 设置此节点是 列表
            node->is_seq = 1;
            // 修改状态：需要解析KEY
            state = CONF_KEY;
        }
        else if (event.type == YAML_SEQUENCE_END_EVENT) {      // 列表结束
            fprintf(stderr, "event.type=YAML_SEQUENCE_END_EVENT; state=%d\n", state);
            done = 1; // 结束，函数退出
        }
        else if (event.type == YAML_MAPPING_START_EVENT) {      // MAPPING 开始事件
            // fprintf(stdout, "event.type=YAML_MAPPING_START_EVENT; state=%d\n", state);
            if (state == CONF_INCLUDE) {
                fprintf(stderr, "Include fields cannot be a mapping: line %zu\n", parser->mark.line);
                goto fail;
            }
            if (inseq) { // 列表里 mapping 的处理方式
                char sequence_node_name[DEFAULT_NAME_LEN];
                // 把 索引作为 路径节点名字， 从 0 开始
                snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
                // 查找对应的 节点
                ConfNode *seq_node = ConfNodeLookupChild(node,
                    sequence_node_name);
                if (seq_node != NULL) {
                    // 先从解析结果里删除，后面再添加
                    TAILQ_REMOVE(&node->head, seq_node, next);
                }
                else {
                    // 没有节点，就创建节点
                    seq_node = ConfNodeNew();
                    if (unlikely(seq_node == NULL)) {
                        goto fail;
                    }
                    seq_node->name = strdup(sequence_node_name);
                    if (unlikely(seq_node->name == NULL)) {
                        free(seq_node);
                        goto fail;
                    }
                }
                seq_node->is_seq = 1;
                TAILQ_INSERT_TAIL(&node->head, seq_node, next);
                // mapping的解析，进入递归调用
                if (ConfYamlParse(parser, seq_node, 0, rlevel, 0) != 0)
                    goto fail;
            }
            else {
                if (ConfYamlParse(parser, node, inseq, rlevel, 0) != 0)
                    goto fail;
            }
            state = CONF_KEY;
        }
        else if (event.type == YAML_MAPPING_END_EVENT) {
            // fprintf(stdout, "event.type=YAML_MAPPING_END_EVENT; state=%d\n", state);
            done = 1; // 结束，函数退出
        }
        else if (event.type == YAML_STREAM_END_EVENT) {
            // fprintf(stdout, "event.type=YAML_STREAM_END_EVENT; state=%d\n", state);
            done = 1; // 结束，函数退出
        }

    next:
        // 清理本次事件，进入下一循环，继续处理数据
        yaml_event_delete(&event);
        continue;

    fail:
        // 失败跳出循环
        yaml_event_delete(&event);
        retval = -1;
        break;
    }

    rlevel--;
    return retval;
}

/**
 * 解析yaml文件
 * 返回值：0 - success, -1 - failure.
 */
int
ConfYamlLoadFile(const char *filename)
{
    FILE *infile;
    yaml_parser_t parser; // yaml解析句柄
    int ret;
    ConfNode *root = ConfGetRootNode();

    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "failed to initialize yaml parser.\n");
        return -1;
    }

    struct stat stat_buf;
    if (stat(filename, &stat_buf) == 0) {
        if (stat_buf.st_mode & S_IFDIR) {
            fprintf(stderr, "yaml argument is not a file but a directory: %s. "
                       "Please specify the yaml file in your -c option.\n",
                    filename);
            yaml_parser_delete(&parser);
            return -1;
        }
    }

    // coverity[toctou : FALSE]
    infile = fopen(filename, "r");
    if (infile == NULL) {
        fprintf(stderr, "failed to open file: %s: %s\n", filename, strerror(errno));
        yaml_parser_delete(&parser);
        return -1;
    }

    if (conf_dirname == NULL) {
        // 设置配置文件路径
        ConfYamlSetConfDirname(filename);
    }

    // 设置要解析的文件句柄
    yaml_parser_set_input_file(&parser, infile);
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
    yaml_parser_delete(&parser);
    fclose(infile);

    return ret;
}

/**
 * 加载解析 yaml文件的 字符串
 */
int
ConfYamlLoadString(const char *string, size_t len)
{
    ConfNode *root = ConfGetRootNode();
    yaml_parser_t parser;
    int ret;

    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "Failed to initialize yaml parser.\n");
        exit(EXIT_FAILURE);
    }
    yaml_parser_set_input_string(&parser, (const unsigned char *)string, len);
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
    yaml_parser_delete(&parser);

    return ret;
}

/**
 * 解析一个文件 filename，把解析的数据放在 prefix 节点下
 * 如果没有此节点，就创建一个空节点，并把解析的数据挂载此节点下。
 *
 * 返回值    0 - 成功
 *          1 - 失败
 */
int
ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
{
    FILE *infile;
    yaml_parser_t parser;
    int ret;
    // 找到这个节点
    ConfNode *root = ConfGetNode(prefix);

    if (yaml_parser_initialize(&parser) != 1) {
        fprintf(stderr, "failed to initialize yaml parser.\n");
        return -1;
    }

    // 检测文件
    struct stat stat_buf;
    if (stat(filename, &stat_buf) == 0) {
        if (stat_buf.st_mode & S_IFDIR) {
            fprintf(stderr, "yaml argument is not a file but a directory: %s. "
                       "Please specify the yaml file in your -c option.\n",
                    filename);
            return -1;
        }
    }

    // 打开文件
    infile = fopen(filename, "r");
    if (infile == NULL) {
        fprintf(stderr, "failed to open file: %s: %s\n", filename, strerror(errno));
        yaml_parser_delete(&parser);
        return -1;
    }

    if (conf_dirname == NULL) {
        ConfYamlSetConfDirname(filename);
    }

    if (root == NULL) { 
        // 没找到此节点，添加一个空节点，值设置为 "<prefix root node>"
        ConfSet(prefix, "<prefix root node>");
        // 再次获取此节点
        root = ConfGetNode(prefix);
        if (root == NULL) {
            // 获取不到就算了，不解析了
            fclose(infile);
            yaml_parser_delete(&parser);
            return -1;
        }
    }
    // 设置yaml文件描述符
    yaml_parser_set_input_file(&parser, infile);
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
    yaml_parser_delete(&parser);
    fclose(infile);

    return ret;
}


